﻿// -----------------------------------------------------------------------
// <copyright file="IndexDto.cs" company="JSC Centido">
//  Copyright (c) JSC Centido. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml;
using System.Xml.Schema;

namespace Centido.Core.Dto
{
    /// <summary>
    ///   A data transfer object for a table index.
    /// </summary>
    public class IndexDto : DataTransferObject
    {
        /// <summary>
        ///   The name of the xml tag that is used to store this object.
        /// </summary>
        public const string XmlTag = "index";

        /// <summary>
        ///   The current xml schema version.
        /// </summary>
        public const string CurrentXmlSchemaVersion = "1";

        /// <summary>
        ///   The name of the xml attribute to store the index uniqueness.
        /// </summary>
        public const string IsUniqueAttribute = "unique";

        /// <summary>
        ///   The name of the tag to store the column list.
        /// </summary>
        public const string ColumnListTag = "columns";

        /// <summary>
        ///   The name of the tag to store the index column.
        /// </summary>
        public const string ColumnTag = "column";

        /// <summary>
        ///   The name of the xml attribute to store the column name.
        /// </summary>
        public const string ColumnNameAttribute = "name";

        /// <summary>
        ///   The indexed columns name list.
        /// </summary>
        private readonly List<string> _columnNameList = new List<string>();


        /// <summary>
        ///   Gets or sets the index uniqueness.
        /// </summary>
        public bool? IsUnique { get; set; }


        /// <summary>
        ///   Gets the indexed columns names.
        /// </summary>
        public ICollection<string> ColumnNames
        {
            get { return _columnNameList; }
        }


        /// <summary>
        ///   Adds the column to the indexed columns list.
        /// </summary>
        /// <param name="columnName">The name of the column to be added.</param>
        public void AddColumn(string columnName)
        {
            if (!_columnNameList.Contains(columnName))
                _columnNameList.Add(columnName);
        }


        /// <summary>
        ///   Deletes the column from the indexed column list.
        /// </summary>
        /// <param name="columnName">The name of the column to be deleted.</param>
        public void DeleteColumn(string columnName)
        {
            if (_columnNameList.Contains(columnName))
                _columnNameList.Remove(columnName);
        }


        /// <summary>
        ///   This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class.
        /// </summary>
        /// <returns>
        ///   An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> method.
        /// </returns>
        public override XmlSchema GetSchema()
        {
            return null;
        }


        /// <summary>
        ///   Generates an object from its XML representation.
        /// </summary>
        /// <param name="reader">The <see cref="T:System.Xml.XmlReader"/> stream from which the object is deserialized. 
        /// </param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="reader"/> is null.</exception>
        public override void ReadXml(XmlReader reader)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");

            reader.MoveToContent();

            // Reading the xml schema version
            var xmlSchemaVersion = ReadStringAttribute(reader, XmlSchemaVersionTag, isRequired: false);

            // If the xml schema version is not defined
            if (string.IsNullOrEmpty(xmlSchemaVersion)
                // or if it is the current xml schema version
                || xmlSchemaVersion == CurrentXmlSchemaVersion)
            {
                // Reading the index uniqueness
                IsUnique = ReadBoolAttribute(reader, IsUniqueAttribute, isRequired: false);

                // Clearing the indexed column list
                _columnNameList.Clear();

                var isEmptyElement = reader.IsEmptyElement;

                reader.ReadStartElement();

                if (!isEmptyElement)
                {
                    // If the indexed columns are specified
                    if (reader.LocalName == ColumnListTag)
                    {
                        isEmptyElement = reader.IsEmptyElement;

                        reader.ReadStartElement();

                        if (!isEmptyElement)
                        {
                            // Reading the indexed columns
                            while (reader.LocalName == ColumnTag)
                            {
                                AddColumn(ReadStringAttribute(reader, ColumnNameAttribute));

                                isEmptyElement = reader.IsEmptyElement;

                                reader.ReadStartElement();

                                if (!isEmptyElement) reader.ReadEndElement();
                            }

                            reader.ReadEndElement();
                        }
                    }

                    reader.ReadEndElement();
                }
            }
            else
                throw new ArgumentException(string.Format(
                    "The specified xml schema version is not supported (node = {0}, version = {1}).",
                    reader.Name,
                    xmlSchemaVersion));
        }


        /// <summary>
        ///   Converts an object into its XML representation.
        /// </summary>
        /// <param name="writer">The <see cref="T:System.Xml.XmlWriter"/> stream to which the object is serialized. 
        /// </param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="writer"/> is null.</exception>
        public override void WriteXml(XmlWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException("writer");

            writer.WriteStartElement(XmlTag);

            // The xml schema version
            writer.WriteAttributeString(XmlSchemaVersionTag, CurrentXmlSchemaVersion);

            // The index uniqueness
            if (IsUnique.HasValue)
                writer.WriteAttributeString(IsUniqueAttribute, 
                    IsUnique.Value.ToString(CultureInfo.InvariantCulture));

            // The indexed columns
            if (_columnNameList.Count > 0)
            {
                writer.WriteStartElement(ColumnListTag);

                foreach (var columnName in _columnNameList)
                {
                    writer.WriteStartElement(ColumnTag);

                    // The column name
                    writer.WriteAttributeString(ColumnNameAttribute, columnName);

                    writer.WriteEndElement();
                }

                writer.WriteEndElement();
            }

            writer.WriteEndElement();
        }


        /// <summary>
        ///   Creates a data transfer object for the specified index.
        /// </summary>
        /// <param name="index">The specified index.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="index"/> is null.</exception>
        /// <returns>Returns the data transfer object for the specified index.</returns>
        public static IndexDto CreateFrom(Index index)
        {
            if (index == null)
                throw new ArgumentNullException("index");

            var indexDto = new IndexDto
            {
                IsUnique = index.IsUnique
            };

            foreach (var column in index.Columns)
                indexDto.AddColumn(column.Name);

            return indexDto;
        }
    }
}
